﻿/*
Copyright (c) 2010 Lance Ward
This software is licensed under the MIT license, except where otherwise noted.
For complete license details, refer to license.txt
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace ADNColorPicker
{
	//TODO: ADD- 
	//color changed event - when the total colors change
	//marker added event / marker selection changed event
	//public property to get/set current marker color & trigger refresh
	//public property to get the colors and stops (colorblend)
	public partial class GradientStrip : UserControl
	{
		private Rectangle preview = new Rectangle();
		private List<GradientMarker> markers = new List<GradientMarker>();
		private Color defaultColor = Color.Black;
		static int markerSize = 10;
		private int selectedMarker = -1;
		private bool isMoving = false;
		public event EventHandler ColorChanged;
		public event EventHandler SelectionChanged;

		public GradientStrip()
		{
			InitializeComponent();
		}

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Color SelectedColor
		{
			get
			{
				if (selectedMarker >= 0) { return markers[selectedMarker].FillColor; }
				else { return Color.Transparent; }
			}
			set
			{
				if (selectedMarker >= 0)
				{
					markers[selectedMarker].FillColor = value;
					this.Invalidate();
					OnColorChanged();
				}
			}
		}

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ColorBlend Gradient
		{
			get { return GetColorBlend(); }
			set 
			{ 
				SetColorBlend(value);
				if (markers.Count - 1 < selectedMarker) { selectedMarker = 0; }
				this.Invalidate();
			}
		}

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Color SolidColor
		{
			get
			{
				if (markers.Count > 0)
				{
					return markers[0].FillColor;
				}
				else { return Color.White; }
			}
			set
			{
                if (markers != null) { markers.Clear(); }
				GradientMarker gm = new GradientMarker(0,0);
				gm.FillColor = value;
				markers.Add(gm);
				if (markers.Count - 1 < selectedMarker) { selectedMarker = 0; }
				this.Invalidate();
			}
		}

		private void GradientStrip_Load(object sender, EventArgs e)
		{
			//add the first marker
			GradientMarker gm = new GradientMarker(preview.Left - (markerSize/2), preview.Bottom + 2);
			gm.FillColor = Color.White;
			gm.Position = 0.0f;
			markers.Add(gm);

			selectedMarker = 0;
		}

		private void OnColorChanged()
		{
			if (ColorChanged != null) { ColorChanged(this, EventArgs.Empty); }
		}

		private void OnSelectionChanged()
		{
			if (SelectionChanged != null) { SelectionChanged(this, EventArgs.Empty); }
		}

		private void GradientStrip_Paint(object sender, PaintEventArgs e)
		{
			DrawGradientPreview(e.Graphics);
			DrawGradientMarkers(e.Graphics);
		}

		private void DrawGradientPreview(Graphics g)
		{
			Point[] previewPoints = new Point[2]{new Point(preview.Left, Math.Abs(preview.Bottom/2)), 
				new Point(preview.Right, Math.Abs(preview.Bottom/2))};
			
			g.FillRectangle(new HatchBrush(HatchStyle.LargeCheckerBoard, Color.LightGray, Color.DarkGray),
					preview);
			if (markers.Count > 1)
			{
				LinearGradientBrush b = new LinearGradientBrush(previewPoints[0], previewPoints[1],
					Color.Transparent, Color.Transparent);
				b.InterpolationColors = GetColorBlend();
				g.FillRectangle(b, preview);
			}
			else if(markers.Count > 0)
			{
				SolidBrush b = new SolidBrush(markers[0].FillColor);
				g.FillRectangle(b, preview);
			}
		}

		private ColorBlend GetColorBlend()
		{
			Color[] markerColors = new Color[markers.Count];
			float[] markerPositions = new float[markers.Count];
			
			for(int i = 0; i < markers.Count; i++)
			{
				markerColors[i] = markers[i].FillColor;
			}

			for (int i = 0; i < markers.Count; i++)
			{
				markerPositions[i] = (float)((markers[i].Location.Left + markers[i].Location.Right)/2)/ (float)preview.Right;
				if (i == 0) { markerPositions[i] = 0.0f; }
				if (i == markers.Count - 1) { markerPositions[i] = 1.0f; }
			}
			
			ColorBlend cb = new ColorBlend(markers.Count);
			cb.Colors = markerColors;
			cb.Positions = markerPositions;

			return cb;
		}

		private void SetColorBlend(ColorBlend colors)
		{
			
            if (markers != null) { markers.Clear(); }
			if (colors == null)
			{ 
				colors = new ColorBlend(); 
				colors.Colors = new Color[1]{Color.White};
				colors.Positions = new float[1]{0.0f};
			}

            for (int i = 0; i < colors.Colors.Length; i++)
            {
                GradientMarker gm = new GradientMarker();
                gm.FillColor = colors.Colors[i];
                if (colors.Positions.Length > i)
                {
					gm.X = (int)(preview.Width * colors.Positions[i]) + (preview.X - (markerSize / 2));
                    gm.Y = preview.Bottom + 2;
					gm.Position = colors.Positions[i];
                }

				markers.Add(gm);
            }
		}

        private void DrawGradientMarkers(Graphics g)
		{
			for(int i = 0; i < markers.Count; i++)
			{
				g.FillRectangle(new SolidBrush(markers[i].FillColor), markers[i].Location);
                if (i == selectedMarker)
                {
                    Pen p = new Pen(Color.Blue, 2.0f);
                    g.DrawRectangle(p, markers[i].Location);
                }
                else
                {
                    Pen p = new Pen(Color.Black, 1.0f);
                    g.DrawRectangle(p, markers[i].Location);
                }
			}

		}

		private void GradientStrip_MouseDoubleClick(object sender, MouseEventArgs e)
		{
			GradientMarker gm = new GradientMarker(e.X, preview.Bottom + 2);
            if (markers.Count < 2)	//the second marker must be at position 1.0, the end of the strip
            {
                gm.X = preview.Right - (markerSize / 2);
                gm.Y = preview.Bottom + 2;
            }

            int index = FindInsertionPoint(gm.X);
            if (index < 0) 
            { 
                markers.Add(gm);
                selectedMarker = markers.Count - 1;
            }
            else
            {
                markers.Insert(index, gm);
                selectedMarker = index;
            }

			OnSelectionChanged();
			OnColorChanged();	//because a color was added
			this.Invalidate();
		}

        private int FindInsertionPoint(int x)
        {
            for (int i = 0; i < markers.Count; i++)
            {
                if(markers[i].X > x){return i;}
            }

            return -1;
        }

        private int HitTestMarkers(int x, int y)
		{
			for (int i = 0; i < markers.Count; i++)
			{
				if (markers[i].Location.Contains(x, y))
				{
					return i;
				}
			}

			return -1;
		}
		private void GradientStrip_MouseDown(object sender, MouseEventArgs e)
		{
			int hitTest = HitTestMarkers(e.X, e.Y);
			if (hitTest >= 0)
			{
				selectedMarker = hitTest;
				//ignore the first and last marker
				if (selectedMarker > 0 && selectedMarker < markers.Count - 1)
				{
					isMoving = true;
				}
			}
			else
			{
				selectedMarker = 0; //always default to 0;
			}
			this.Invalidate();
			OnSelectionChanged();
		}

		private void GradientStrip_MouseMove(object sender, MouseEventArgs e)
		{
			if (selectedMarker >= 0 && isMoving)
			{
				markers[selectedMarker].X = e.X;
				//TODO - recalculate Position
				this.Invalidate();
			}
		}

		private void GradientStrip_MouseUp(object sender, MouseEventArgs e)
		{
			isMoving = false;
			this.Invalidate();
		}

		private void GradientStrip_Resize(object sender, EventArgs e)
		{
			preview = this.ClientRectangle;
			preview.Inflate(-markerSize / 2, -(markerSize / 2) - 2);
			preview.Location = new Point(preview.Location.X, 0);

			PositionMarkers();
			this.Invalidate();
		}

		private void PositionMarkers()
		{
			for(int i = 0; i < markers.Count; i++)
			{
				markers[i].X = (int)(preview.Width * markers[i].Position) + (preview.X - (markerSize / 2));
				markers[i].Y = preview.Bottom + 2;
			}
		}
	}

	public class GradientMarker
	{
		static int markerSize = 8;
        private float position = 0.0f;
		private Rectangle location = new Rectangle(0, 0, markerSize, markerSize);
		private Color fillColor = Color.Black;

        public GradientMarker() { }

		public GradientMarker(int x, int y)
		{
			location.X = x;
			location.Y = y;
			location.Height = markerSize;
			location.Width = markerSize;

			fillColor = Color.Black;
		}

		public GradientMarker(int x, int y, Color color)
		{
			location.X = x;
			location.Y = y;
			location.Height = markerSize;
			location.Width = markerSize;

			fillColor = color;
		}

		public Rectangle Location
		{
			get
			{
				return location;
			}
			set
			{
				location = value;
			}
		}

		public int X
		{
			get
			{
				return location.X;
			}
			set
			{
				location.X = value;
			}
		}

        public int Y
        {
            get
            {
                return location.Y;
            }
            set
            {
                location.Y = value;
            }
        }

		public Color FillColor
		{
			get
			{
				return fillColor;
			}
			set
			{
				fillColor = value;
			}
		}

        public float Position
        {
            get { return position; }
            set { position = value; }
        }
	}
}
